﻿/*MyPhotoIndex Free Photo Organizer <www.myphotoindex.com>
  Copyright (C) <2006-2009>  <Asaf Yarkoni asaf@yarkoni.net>

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.*/
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Net;
using System.Threading;
using MyPhotoIndex.Interfaces.Imaging;
using MyPhotoIndex.ImageBank;
using MyPhotoIndex.Utilities;
using System.Web;
using System.Globalization;
using MyPhotoIndex.Interfaces.Tagging;
using System.Xml.Serialization;
using MyPhotoIndex.ImageBank.Query;
using System.Drawing;
using MyPhotoIndex.Plugins.WebHost.RequestHandlers;
using blowery.Web.HttpCompress;
using MyPhotoIndex.Plugins.WebHost.Security;
using MyPhotoIndex.Utilities.Network_security;
using System.Reflection;
using MyPhotoIndex.Utilities.Network;

namespace MyPhotoIndex.Plugins.WebHost
{
    public class HttpHost : IDisposable
    {
        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing == true)
            {
                Stop();
                // Free other state (managed objects).
                if (m_listener != null)
                {
                }
            }
            // Free your own state (unmanaged objects).
            // Set large fields to null.
        }

        ~HttpHost()
        {
            Dispose(false);
        }

        #endregion

        public event EventHandler IsRunningChanged;

        private bool m_isOnline = false;
        private Thread m_listenerThread;
        private HttpListener m_listener;
        List<string> m_baseUrls = new List<string>();

        private bool m_useAuthentication;

        public bool UseAuthentication
        {
            get { return m_useAuthentication; }
            set { m_useAuthentication = value; }
        }

        private string m_loginPage = "/LoginPage.mvp";

        public List<string> ServerAddress
        {
            get { return m_baseUrls; }
        }

        private int m_port = 8080;

        public int Port
        {
            get { return m_port; }
            set { m_port = value; }
        }

        public bool IsRunning
        {
            get { return m_isOnline; }
            private set
            {
                m_isOnline = value;
                if (IsRunningChanged != null)
                {
                    IsRunningChanged.Invoke(this, EventArgs.Empty);
                }
            }
        }

        private Stats m_stats = new Stats();

        public Stats Stats
        {
            get { return m_stats; }
        }

        public HttpHost(int port)
        {
            m_port = port;

            RequestProcessor.RegisterHandler("/", new GenericRequestHandler());
            RequestProcessor.RegisterHandler("/tags", new TagsListRequestHandler());
            RequestProcessor.RegisterHandler("/imageList", new ImageListRequestHandler());
            RequestProcessor.RegisterHandler("/thumbnails", new ThumbnailsRequestHandler());
            RequestProcessor.RegisterHandler("/images", new LargeImageRequestHandler());
        }

        private void ListnerThreadProc()
        {
            try
            {
                if (HttpListener.IsSupported == false)
                {
                    //  Log.Warn("Windows XP SP2 or Server 2003 is required to use the HttpListener class.");
                    Logger.Log("Windows XP SP2 or Server 2003 is required to use the HttpListener class.");
                    return;
                }

                HandleSecurity(true);

                // Create a listener.
                using (m_listener = new HttpListener())
                {
                    m_listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;

                    bool status = TryStart();
                    if (status == false)
                    {
                        IsRunning = false;
                        return;
                    }

                    while (m_isOnline == true)
                    {
                        ProcessRequest();
                    }

                    m_listener.Stop();
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }
        }

        private bool TryStart()
        {
            try
            {
                m_baseUrls.Clear();

                string hostName = Dns.GetHostName();
                m_baseUrls.Add("http://" + hostName + ":" + m_port + "/");
                m_baseUrls.Add("http://" + IPAddress.Loopback.ToString() + ":" + m_port + "/");

                IPHostEntry ipEntry = Dns.GetHostEntry(hostName);

                foreach (IPAddress ipAddress in ipEntry.AddressList)
                {
                    string ipS = ipAddress.ToString();
                    if (ipS.Length <= 15)
                    {
                        m_baseUrls.Add("http://" + ipS + ":" + m_port + "/");
                    }
                }

                foreach (string url in m_baseUrls)
                {
                    try
                    {
                        m_listener.Prefixes.Add(url);
                    }
                    catch (Exception ex)
                    {
                        Logger.Log(ex);
                    }
                }

                m_listener.Start();
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
                return false;
            }

            return true;
        }

        public void Stop()
        {
            HandleSecurity(false);

            m_stats.Stop();
            IsRunning = false;
            if (m_listener != null && m_listener.IsListening == true)
            {
                m_listener.Stop();
            }

            if (m_listenerThread != null)
            {
                if (m_listenerThread.IsAlive == true)
                {
                    m_listenerThread.Abort();
                }
            }
        }

        public void Start()
        {
            if (IsRunning == true)
            {
                return;
            }

            m_listenerThread = new Thread(ListnerThreadProc);
            m_listenerThread.Start();
            m_stats.Start();

            IsRunning = true;
        }

        public void ProcessRequest()
        {
            try
            {
                // Note: The GetContext method blocks while waiting for a request. 
                HttpListenerContext context = m_listener.GetContext();
                HttpListenerRequest request = context.Request;

                m_stats.UpdadeRequestCounter(1);

                if (m_useAuthentication == true)
                {
                    bool isAuthenticated = Authentication.IsAuthenticated(request);
                    if (isAuthenticated == false &&
                        request.Url.AbsolutePath.CompareTo(m_loginPage) != 0
                        && request.Url.AbsolutePath.StartsWith("/skin") == false)
                    {
                        context.Response.Redirect(m_loginPage + "?src=" + HttpUtility.UrlEncode(request.Url.OriginalString));
                        context.Response.Close();

                        return;
                    }
                }

                ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessMessage), context);
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
            }
        }

        private void ProcessMessage(object state)
        {
            HttpListenerContext context = state as HttpListenerContext;

            // Obtain a response object.
            HttpListenerResponse response = context.Response;
            response.SendChunked = true;

            try
            {
                HttpListenerRequest request = context.Request;
                response.AddHeader("Server", "My Photo Index");

                string path = Path.GetDirectoryName(request.Url.AbsolutePath);
                if (string.IsNullOrEmpty(path) == true)
                {
                    path = "/";
                }
                else
                {
                    path = path.Replace(Path.DirectorySeparatorChar, '/');
                }

                IRequestHandler requestHandler = RequestProcessor.GetHandler(path);
                requestHandler.Process(request, response);
            }
            catch (Exception ex)
            {
                Logger.Log(ex);
                response.Close();
            }
        }

        private void HandleSecurity(bool install)
        {
            if (install)
            {
                if (WebHostPluginSettings.Default.OpenFirewallPort == true)
                {
                    Logger.Log("Attempting to configure windows firewall Open ports");
                    try
                    {
                        Firewall.OpenFirewallPorts(Assembly.GetEntryAssembly().Location, "My Photo Index Web Interface", new int[] { m_port });
                        Logger.Log("windows firewall configured Open ports");
                    }
                    catch (Exception ex)
                    {
                        Logger.Log("Open windows firewall port failed", ex);
                    }
                }

                if (WebHostPluginSettings.Default.OpenNATPortForwarding == true)
                {
                    try
                    {
                        Logger.Log("Attempting to configure UPnP Open ports");
                        if (UPnPNATHelper.Add(m_port, "TCP", m_port, NetworkHelper.LocalIPAddress, true, "My Photo Index Web Interface") == true)
                        {
                            Logger.Log("UPNP device responded with positive for port " + m_port.ToString());
                        }
                        else
                        {
                            Logger.Log("UPNP device responded with negative for port " + m_port.ToString());
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.Log("Set NAT Port forwarding failed UPnPNATHelper.Add", ex);
                    }
                }
            }
            else
            {
                try
                {
                    Logger.Log("Attempting to configure UPnP Close ports");
                    UPnPNATHelper.Remove(m_port, "TCP");

                    //  NAT.ForwardPort(m_port, System.Net.Sockets.ProtocolType.Tcp, "My Photo Index Web Interface");
                    Logger.Log("UPNP device responded with positive for closing port " + m_port.ToString());
                }
                catch (Exception ex)
                {
                    Logger.Log("Set NAT Port forwarding failed UPnPNATHelper.Remove", ex);
                }

                Logger.Log("Attempting to configure windows firewall");
                try
                {
                    Firewall.CloseFirewallPorts(Assembly.GetEntryAssembly().Location, "My Photo Index Web Interface", new int[] { m_port });
                    Logger.Log("windows firewall configured Open ports");
                }
                catch (Exception ex)
                {
                    Logger.Log("Close windows firewall port failed", ex);
                }
            }
        }
    }
}
